// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/abi"
	"internal/cpu"
	"internal/goarch"
	"internal/goos"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

/*
Stack layout parameters.
Included both by runtime (compiled via 6c) and linkers (compiled via gcc).

The per-goroutine g->stackguard is set to point StackGuard bytes
above the bottom of the stack.  Each function compares its stack
pointer against g->stackguard to check for overflow.  To cut one
instruction from the check sequence for functions with tiny frames,
the stack is allowed to protrude StackSmall bytes below the stack
guard.  Functions with large frames don't bother with the check and
always call morestack.  The sequences are (for amd64, others are
similar):

	guard = g->stackguard
	frame = function's stack frame size
	argsize = size of function arguments (call + return)

	stack frame size <= StackSmall:
		CMPQ guard, SP
		JHI 3(PC)
		MOVQ m->morearg, $(argsize << 32)
		CALL morestack(SB)

	stack frame size > StackSmall but < StackBig
		LEAQ (frame-StackSmall)(SP), R0
		CMPQ guard, R0
		JHI 3(PC)
		MOVQ m->morearg, $(argsize << 32)
		CALL morestack(SB)

	stack frame size >= StackBig:
		MOVQ m->morearg, $((argsize << 32) | frame)
		CALL morestack(SB)

The bottom StackGuard - StackSmall bytes are important: there has
to be enough room to execute functions that refuse to check for
stack overflow, either because they need to be adjacent to the
actual caller's frame (deferproc) or because they handle the imminent
stack overflow (morestack).

For example, deferproc might call malloc, which does one of the
above checks (without allocating a full frame), which might trigger
a call to morestack.  This sequence needs to fit in the bottom
section of the stack.  On amd64, morestack's frame is 40 bytes, and
deferproc's frame is 56 bytes.  That fits well within the
StackGuard - StackSmall bytes at the bottom.
The linkers explore all possible call traces involving non-splitting
functions to make sure that this limit cannot be violated.
*/

const (
	// StackSystem是一个额外的字节数，用于将
	// 添加到通常保护区下方的每个堆栈中，用于特定于操作系统的
	// 目的，如信号处理。用于Windows、Plan 9、
	// 和iOS，因为它们不使用单独的堆栈。
	_StackSystem = goos.IsWindows*512*goarch.PtrSize + goos.IsPlan9*512 + goos.IsIos*goarch.IsArm64*1024

	// Go代码使用的最小堆栈大小
	_StackMin = 2048

	// 要分配的最小堆栈大小。
	// 这里的黑客将FixedStack0的次幂提高到2。
	_FixedStack0 = _StackMin + _StackSystem
	_FixedStack1 = _FixedStack0 - 1
	_FixedStack2 = _FixedStack1 | (_FixedStack1 >> 1)
	_FixedStack3 = _FixedStack2 | (_FixedStack2 >> 2)
	_FixedStack4 = _FixedStack3 | (_FixedStack3 >> 4)
	_FixedStack5 = _FixedStack4 | (_FixedStack4 >> 8)
	_FixedStack6 = _FixedStack5 | (_FixedStack5 >> 16)
	_FixedStack  = _FixedStack6 + 1

	// 需要大于此值的帧的函数使用额外的
	// 指令来执行堆栈拆分检查，以避免在SP-framesize包装小于零时溢出
	// 。
	// 此值不能大于零处未映射的
	// 空间的大小。
	_StackBig = 4096

	// 堆栈保护是一个指针，位于堆栈底部上方这么多字节。
	// 
	// 警卫为一个_stacksmallframe加上
	// a _stacklimitnosplit调用链加上操作系统的_stacksystem
	// 字节留出了足够的空间。
	_StackGuard = 928*sys.StackGuardMultiplier + _StackSystem

	// 在堆栈拆分检查后，SP被允许是这个
	// 堆栈保护下的许多字节。这会将指令
	// 保存在小帧的检查序列中。
	_StackSmall = 128

	// 一个NOSPLIT链
	// 函数可以使用的最大字节数。
	_StackLimit = _StackGuard - _StackSystem - _StackSmall
)

const (
	// stackDebug==0:不记录
	// ==1:记录每堆栈操作
	// ==2:记录每帧操作
	// ==3:记录每字更新
	// ==4:记录每字读取
	stackDebug       = 0
	stackFromSystem  = 0 // /从系统内存而不是内存分配堆栈堆
	stackFaultOnFree = 0 // 旧堆栈映射为noaccess以检测空闲后的使用
	stackPoisonCopy  = 0 // 填充不应使用垃圾访问的堆栈，以检测复制期间的错误解引用
	stackNoCache     = 0 // 禁用per-P小堆栈缓存

	// 在回溯期间检查BP链接。
	debugCheckBP = false
)

const (
	uintptrMask = 1<<(8*goarch.PtrSize) - 1

	// 可以将以下值存储到g.stackguard0，以强制
	// 下一次堆栈检查失败。
	// 这些都比任何实际SP都大。

	// Goroutine抢占请求。
	// 0xFFFFFFFFFade in hex。
	stackPreempt = uintptrMask & -1314

	// 线程正在分叉。导致拆分堆栈检查失败。
	// 0xfffffb2e十六进制。
	stackFork = uintptrMask & -1234

	// 强制堆栈移动。用于调试。
	// 0xFFFFFFFEED十六进制格式。
	stackForceMove = uintptrMask & -275

	// StackToximin是允许的最低堆栈毒药值。
	stackPoisonMin = uintptrMask & -4096
)

// 具有空闲堆栈的跨度的全局池。
// 堆栈根据大小分配顺序。
// 订单=log_2（大小/固定堆栈）
// 每个订单都有一个自由列表。
var stackpool [_NumStackOrders]struct {
	item stackpoolItem
	_    [cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize]byte
}

// go:notinheap 
type stackpoolItem struct {
	mu   mutex
	span mSpanList
}

// /大堆栈跨度的全局池。
var stackLarge struct {
	lock mutex
	free [heapAddrBits - pageShift]mSpanList // 按日志2（s.npages）列出的自由列表
}

func stackinit() {
	if _StackCacheSize&_PageMask != 0 {
		throw("cache size must be a multiple of page size")
	}
	for i := range stackpool {
		stackpool[i].item.span.init()
		lockInit(&stackpool[i].item.mu, lockRankStackpool)
	}
	for i := range stackLarge.free {
		stackLarge.free[i].init()
		lockInit(&stackLarge.lock, lockRankStackLarge)
	}
}

// stacklog2返回⌊日志2（n）⌋. 
func stacklog2(n uintptr) int {
	log2 := 0
	for n > 1 {
		n >>= 1
		log2++
	}
	return log2
}

// 从空闲池分配堆栈。必须使用
// stackpool[order]调用。项目穆举着。
func stackpoolalloc(order uint8) gclinkptr {
	list := &stackpool[order].item.span
	s := list.first
	lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)
	if s == nil {
		// 没有可用堆栈。分配另一个跨度值。
		s = mheap_.allocManual(_StackCacheSize>>_PageShift, spanAllocStack)
		if s == nil {
			throw("out of memory")
		}
		if s.allocCount != 0 {
			throw("bad allocCount")
		}
		if s.manualFreeList.ptr() != nil {
			throw("bad manualFreeList")
		}
		osStackAlloc(s)
		s.elemsize = _FixedStack << order
		for i := uintptr(0); i < _StackCacheSize; i += s.elemsize {
			x := gclinkptr(s.base() + i)
			x.ptr().next = s.manualFreeList
			s.manualFreeList = x
		}
		list.insert(s)
	}
	x := s.manualFreeList
	if x.ptr() == nil {
		throw("span has no free stacks")
	}
	s.manualFreeList = x.ptr().next
	s.allocCount++
	if s.manualFreeList.ptr() == nil {
		// s中的所有堆栈都已分配。
		list.remove(s)
	}
	return x
}

// 将堆栈x添加到空闲池。必须使用stackpool[order]调用。项目穆举着。
func stackpoolfree(x gclinkptr, order uint8) {
	s := spanOfUnchecked(uintptr(x))
	if s.state.get() != mSpanManual {
		throw("freeing stack not in a stack span")
	}
	if s.manualFreeList.ptr() == nil {
		// s现在将有一个空闲堆栈
		stackpool[order].item.span.insert(s)
	}
	x.ptr().next = s.manualFreeList
	s.manualFreeList = x
	s.allocCount--
	if gcphase == _GCoff && s.allocCount == 0 {
		// Span完全免费。如果我们正在清扫，立刻把它放回垃圾堆
		// 。
		// 
		// 如果GC处于活动状态，我们将空闲时间延迟到
		// GC结束，以避免以下情况：
		// 
		// 1）GC启动，扫描SudoG，但尚未标记SudoG。元素指针
		// 2）指针指向的堆栈被复制
		// 3）旧堆栈被释放
		// 4）包含的范围被标记为自由
		// 5）GC尝试标记SudoG。埃伦指针。
		// 标记失败，因为该指针看起来像是进入自由范围的
		// 指针。
		// 
		// 通过不释放，我们阻止了第4步，直到GC完成。
		stackpool[order].item.span.remove(s)
		s.manualFreeList = 0
		osStackFree(s)
		mheap_.freeManual(s, spanAllocStack)
	}
}

// stackcacherefill/stackcacherelease实现堆栈段的全局池。
// 需要该池来防止每线程缓存的无限增长。
// 
// go:systemstack 
func stackcacherefill(c *mcache, order uint8) {
	if stackDebug >= 1 {
		print("stackcacherefill order=", order, "\n")
	}

	// 从全局缓存中获取一些堆栈。
	// 抓取允许容量的一半（防止颠簸）。
	var list gclinkptr
	var size uintptr
	lock(&stackpool[order].item.mu)
	for size < _StackCacheSize/2 {
		x := stackpoolalloc(order)
		x.ptr().next = list
		list = x
		size += _FixedStack << order
	}
	unlock(&stackpool[order].item.mu)
	c.stackcache[order].list = list
	c.stackcache[order].size = size
}

// go:systemstack 
func stackcacherelease(c *mcache, order uint8) {
	if stackDebug >= 1 {
		print("stackcacherelease order=", order, "\n")
	}
	x := c.stackcache[order].list
	size := c.stackcache[order].size
	lock(&stackpool[order].item.mu)
	for size > _StackCacheSize/2 {
		y := x.ptr().next
		stackpoolfree(x, order)
		x = y
		size -= _FixedStack << order
	}
	unlock(&stackpool[order].item.mu)
	c.stackcache[order].list = x
	c.stackcache[order].size = size
}

// go:systemstack 
func stackcache_clear(c *mcache) {
	if stackDebug >= 1 {
		print("stackcache clear\n")
	}
	for order := uint8(0); order < _NumStackOrders; order++ {
		lock(&stackpool[order].item.mu)
		x := c.stackcache[order].list
		for x.ptr() != nil {
			y := x.ptr().next
			stackpoolfree(x, order)
			x = y
		}
		c.stackcache[order].list = 0
		c.stackcache[order].size = 0
		unlock(&stackpool[order].item.mu)
	}
}

// stackalloc分配一个n字节堆栈。
// 
// stackalloc必须在系统堆栈上运行，因为它使用per-P 
// 资源，并且不能拆分堆栈。
// 
// go:systemstack 
func stackalloc(n uint32) stack {
	// 必须在调度程序堆栈上调用Stackalloc，这样我们
	// 在Stackalloc运行的代码期间，永远不要尝试增加堆栈。
	// 这样做会导致僵局（第1547期）。
	thisg := getg()
	if thisg != thisg.m.g0 {
		throw("stackalloc not on scheduler stack")
	}
	if n&(n-1) != 0 {
		throw("stack size not a power of 2")
	}
	if stackDebug >= 1 {
		print("stackalloc ", n, "\n")
	}

	if debug.efence != 0 || stackFromSystem != 0 {
		n = uint32(alignUp(uintptr(n), physPageSize))
		v := sysAlloc(uintptr(n), &memstats.stacks_sys)
		if v == nil {
			throw("out of memory (stackalloc)")
		}
		return stack{uintptr(v), uintptr(v) + uintptr(n)}
	}

	// 使用固定大小的自由列表分配器分配小堆栈。
	// 如果我们需要一个更大的堆栈，我们可以依靠分配一个专用的跨度。
	var v unsafe.Pointer
	if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {
		order := uint8(0)
		n2 := n
		for n2 > _FixedStack {
			order++
			n2 >>= 1
		}
		var x gclinkptr
		if stackNoCache != 0 || thisg.m.p == 0 || thisg.m.preemptoff != "" {
			// 这个。m、 p==0可能发生在exitsyscall 
			// 或procresize的内部。只需从全局池中获取一个堆栈。
			// 在gc期间也不要触摸堆栈缓存
			// 因为它是同时刷新的。
			lock(&stackpool[order].item.mu)
			x = stackpoolalloc(order)
			unlock(&stackpool[order].item.mu)
		} else {
			c := thisg.m.p.ptr().mcache
			x = c.stackcache[order].list
			if x.ptr() == nil {
				stackcacherefill(c, order)
				x = c.stackcache[order].list
			}
			c.stackcache[order].list = x.ptr().next
			c.stackcache[order].size -= uintptr(n)
		}
		v = unsafe.Pointer(x)
	} else {
		var s *mspan
		npage := uintptr(n) >> _PageShift
		log2npage := stacklog2(npage)

		// 尝试从大堆栈缓存中获取堆栈。
		lock(&stackLarge.lock)
		if !stackLarge.free[log2npage].isEmpty() {
			s = stackLarge.free[log2npage].first
			stackLarge.free[log2npage].remove(s)
		}
		unlock(&stackLarge.lock)

		lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)

		if s == nil {
			// 从堆中分配一个新堆栈。
			s = mheap_.allocManual(npage, spanAllocStack)
			if s == nil {
				throw("out of memory")
			}
			osStackAlloc(s)
			s.elemsize = uintptr(n)
		}
		v = unsafe.Pointer(s.base())
	}

	if raceenabled {
		racemalloc(v, uintptr(n))
	}
	if msanenabled {
		msanmalloc(v, uintptr(n))
	}
	if asanenabled {
		asanunpoison(v, uintptr(n))
	}
	if stackDebug >= 1 {
		print("  allocated ", v, "\n")
	}
	return stack{uintptr(v), uintptr(v) + uintptr(n)}
}

// stackfree在stk释放n字节堆栈分配。
// 
// stackfree必须在系统堆栈上运行，因为它使用per-P 
// 资源，并且不能拆分堆栈。
// 
// go:systemstack 
func stackfree(stk stack) {
	gp := getg()
	v := unsafe.Pointer(stk.lo)
	n := stk.hi - stk.lo
	if n&(n-1) != 0 {
		throw("stack not a power of 2")
	}
	if stk.lo+n < stk.hi {
		throw("bad stack size")
	}
	if stackDebug >= 1 {
		println("stackfree", v, n)
		memclrNoHeapPointers(v, n) // 对于测试，请删除堆栈数据
	}
	if debug.efence != 0 || stackFromSystem != 0 {
		if debug.efence != 0 || stackFaultOnFree != 0 {
			sysFault(v, n)
		} else {
			sysFree(v, n, &memstats.stacks_sys)
		}
		return
	}
	if msanenabled {
		msanfree(v, n)
	}
	if asanenabled {
		asanpoison(v, n)
	}
	if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {
		order := uint8(0)
		n2 := n
		for n2 > _FixedStack {
			order++
			n2 >>= 1
		}
		x := gclinkptr(v)
		if stackNoCache != 0 || gp.m.p == 0 || gp.m.preemptoff != "" {
			lock(&stackpool[order].item.mu)
			stackpoolfree(x, order)
			unlock(&stackpool[order].item.mu)
		} else {
			c := gp.m.p.ptr().mcache
			if c.stackcache[order].size >= _StackCacheSize {
				stackcacherelease(c, order)
			}
			x.ptr().next = c.stackcache[order].list
			c.stackcache[order].list = x
			c.stackcache[order].size += n
		}
	} else {
		s := spanOfUnchecked(uintptr(v))
		if s.state.get() != mSpanManual {
			println(hex(s.base()), v)
			throw("bad span state")
		}
		if gcphase == _GCoff {
			// 如果我们正在进行
			// 扫描，请立即释放堆栈。
			osStackFree(s)
			mheap_.freeManual(s, spanAllocStack)
		} else {
			// 如果GC正在运行，我们不能将
			// 堆栈范围返回到堆中，因为它可能被
			// 重新用作堆范围，而这种状态
			// 更改将与GC竞争。将其添加到
			// 大堆栈缓存中。
			log2npage := stacklog2(s.npages)
			lock(&stackLarge.lock)
			stackLarge.free[log2npage].insert(s)
			unlock(&stackLarge.lock)
		}
	}
}

var maxstacksize uintptr = 1 << 20 // 在运行前就足够了。main设置为real 

var maxstackceiling = maxstacksize

var ptrnames = []string{
	0: "scalar",
	1: "ptr",
}

// Stack frame layout 
// 
// （x86）
// +-------------+
// ；args from caller | 
// +-------------+<-frame->argp 
// ；return address | 
// /+--------------+<-frame->varp 
// /| locals | 
// /+--------------+
// /| args to callee | 
// /..
// /.（arm）
// |呼叫方的重新地址| 
// /+--------------+<-frame->varp 
// /|本地人| 
// /+-------------+
// /|呼叫方的参数| 

type adjustinfo struct {
	old   stack
	cache pcvalueCache

	// sghi是最高的sudog。栈上的元素。
	sghi uintptr
}

// Adjustpointer检查*vpp是否在adjinfo描述的旧堆栈中。
// 如果是这样，它会重写*vpp以指向新堆栈。
func adjustpointer(adjinfo *adjustinfo, vpp unsafe.Pointer) {
	pp := (*uintptr)(vpp)
	p := *pp
	if stackDebug >= 4 {
		print("        ", pp, ":", hex(p), "\n")
	}
	if adjinfo.old.lo <= p && p < adjinfo.old.hi {
		*pp = p + adjinfo.delta
		if stackDebug >= 3 {
			print("        adjust ptr ", pp, ":", hex(p), " -> ", hex(*pp), "\n")
		}
	}
}

// 来自编译器的有关堆栈帧布局的信息。
// 注意：此类型必须与reflect一致。位向量。
type bitvector struct {
	n        int32 // /#位
	bytedata *uint8
}

// ptrbit返回bv中的第i位。
// ptrbit的效率低于直接在位向量位上迭代，
// ，并且只应在非性能关键代码中使用。
// 有关位向量的高效遍历示例，请参见adjustpointers。
func (bv *bitvector) ptrbit(i uintptr) uint8 {
	b := *(addb(bv.bytedata, i/8))
	return (b >> (i % 8)) & 1
}

// bv描述从地址scanp开始的内存。
// 调整其中包含的任何指针。
func adjustpointers(scanp unsafe.Pointer, bv *bitvector, adjinfo *adjustinfo, f funcInfo) {
	minp := adjinfo.old.lo
	maxp := adjinfo.old.hi
	delta := adjinfo.delta
	num := uintptr(bv.n)
	// 如果此帧可能包含通道接收插槽，请使用CAS 
	// 调整指针。如果插槽尚未接收到
	// 中，它可能包含堆栈指针，而并发发送
	// 可能会与调整这些指针竞争。（发送值
	// 本身不能包含堆栈指针。）
	useCAS := uintptr(scanp) < adjinfo.sghi
	for i := uintptr(0); i < num; i += 8 {
		if stackDebug >= 4 {
			for j := uintptr(0); j < 8; j++ {
				print("        ", add(scanp, (i+j)*goarch.PtrSize), ":", ptrnames[bv.ptrbit(i+j)], ":", hex(*(*uintptr)(add(scanp, (i+j)*goarch.PtrSize))), " # ", i, " ", *addb(bv.bytedata, i/8), "\n")
			}
		}
		b := *(addb(bv.bytedata, i/8))
		for b != 0 {
			j := uintptr(sys.Ctz8(b))
			b &= b - 1
			pp := (*uintptr)(add(scanp, (i+j)*goarch.PtrSize))
		retry:
			p := *pp
			if f.valid() && 0 < p && p < minLegalPointer && debug.invalidptr != 0 {
				// 看起来像指针槽中的垃圾值。
				// 现场分析错误？
				getg().m.traceback = 2
				print("runtime: bad pointer in frame ", funcname(f), " at ", pp, ": ", hex(p), "\n")
				throw("invalid pointer found on stack")
			}
			if minp <= p && p < maxp {
				if stackDebug >= 3 {
					print("adjust ptr ", hex(p), " ", funcname(f), "\n")
				}
				if useCAS {
					ppu := (*unsafe.Pointer)(unsafe.Pointer(pp))
					if !atomic.Casp1(ppu, unsafe.Pointer(p), unsafe.Pointer(p+delta)) {
						goto retry
					}
				} else {
					*pp = p + delta
				}
			}
		}
	}
}

// 注意：参数/返回区域由被调用方调整。
func adjustframe(frame *stkframe, arg unsafe.Pointer) bool {
	adjinfo := (*adjustinfo)(arg)
	if frame.continpc == 0 {
		// 框架已失效。
		return true
	}
	f := frame.fn
	if stackDebug >= 2 {
		print("    adjusting ", funcname(f), " frame=[", hex(frame.sp), ",", hex(frame.fp), "] pc=", hex(frame.pc), " continpc=", hex(frame.continpc), "\n")
	}
	if f.funcID == funcID_systemstack_switch {
		// 位于goroutine堆栈底部的一个特殊例程，用于执行systemstack调用。
		// 即使我们没有完整的GC信息（因为它是用asm编写的），我们也允许复制它。
		return true
	}

	locals, args, objs := getStackMap(frame, &adjinfo.cache, true)

	// 如果已分配堆栈帧，则调整局部变量。
	if locals.n > 0 {
		size := uintptr(locals.n) * goarch.PtrSize
		adjustpointers(unsafe.Pointer(frame.varp-size), &locals, adjinfo, f)
	}

	// 如果有保存的基指针，请调整它。
	// 如何调整arm64帧指针？
	if goarch.ArchFamily == goarch.AMD64 && frame.argp-frame.varp == 2*goarch.PtrSize {
		if stackDebug >= 3 {
			print("      saved bp\n")
		}
		if debugCheckBP {
			// 帧指针应始终指向Go堆栈上的下一个更高的帧（或为零，表示堆栈上的顶部帧）。
			bp := *(*uintptr)(unsafe.Pointer(frame.varp))
			if bp != 0 && (bp < adjinfo.old.lo || bp >= adjinfo.old.hi) {
				println("runtime: found invalid frame pointer")
				print("bp=", hex(bp), " min=", hex(adjinfo.old.lo), " max=", hex(adjinfo.old.hi), "\n")
				throw("bad frame pointer")
			}
		}
		adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
	}

	// 调整参数。
	if args.n > 0 {
		if stackDebug >= 3 {
			print("      args\n")
		}
		adjustpointers(unsafe.Pointer(frame.argp), &args, adjinfo, funcInfo{})
	}

	// 调整所有堆栈对象中的指针（无论它们是否处于活动状态）。
	// 参见mgcmark中的注释。go:scanframeworker。
	if frame.varp != 0 {
		for i := range objs {
			obj := &objs[i]
			off := obj.off
			base := frame.varp // 局部变量基指针
			if off >= 0 {
				base = frame.argp // 参数和返回值基指针
			}
			p := base + uintptr(off)
			if p < frame.sp {
				// 对象尚未在帧中分配。
				// （当堆栈边界检查失败并且
				// 我们调用morestack时发生。）
				continue
			}
			ptrdata := obj.ptrdata()
			gcdata := obj.gcdata()
			var s *mspan
			if obj.useGCProg() {
				// 参见mgcmark中的注释。go:scanstack 
				s = materializeGCProg(ptrdata, gcdata)
				gcdata = (*byte)(unsafe.Pointer(s.startAddr))
			}
			for i := uintptr(0); i < ptrdata; i += goarch.PtrSize {
				if *addb(gcdata, i/(8*goarch.PtrSize))>>(i/goarch.PtrSize&7)&1 != 0 {
					adjustpointer(adjinfo, unsafe.Pointer(p+i))
				}
			}
			if s != nil {
				dematerializeGCProg(s)
			}
		}
	}

	return true
}

func adjustctxt(gp *g, adjinfo *adjustinfo) {
	adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.ctxt))
	if !framepointer_enabled {
		return
	}
	if debugCheckBP {
		bp := gp.sched.bp
		if bp != 0 && (bp < adjinfo.old.lo || bp >= adjinfo.old.hi) {
			println("runtime: found invalid top frame pointer")
			print("bp=", hex(bp), " min=", hex(adjinfo.old.lo), " max=", hex(adjinfo.old.hi), "\n")
			throw("bad top frame pointer")
		}
	}
	adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.bp))
}

func adjustdefers(gp *g, adjinfo *adjustinfo) {
	// 调整延迟结构中的指针。
	// 我们需要先这样做，因为我们需要调整
	// 延迟。链接字段，以便我们始终在新堆栈上工作。
	adjustpointer(adjinfo, unsafe.Pointer(&gp._defer))
	for d := gp._defer; d != nil; d = d.link {
		adjustpointer(adjinfo, unsafe.Pointer(&d.fn))
		adjustpointer(adjinfo, unsafe.Pointer(&d.sp))
		adjustpointer(adjinfo, unsafe.Pointer(&d._panic))
		adjustpointer(adjinfo, unsafe.Pointer(&d.link))
		adjustpointer(adjinfo, unsafe.Pointer(&d.varp))
		adjustpointer(adjinfo, unsafe.Pointer(&d.fd))
	}
}

func adjustpanics(gp *g, adjinfo *adjustinfo) {
	// 恐慌已在堆栈中，并已调整。
	// 更新指向G中列表头的指针。
	adjustpointer(adjinfo, unsafe.Pointer(&gp._panic))
}

func adjustsudogs(gp *g, adjinfo *adjustinfo) {
	// SudoG结构
	// 指向的数据元素可能在堆栈中。
	for s := gp.waiting; s != nil; s = s.waitlink {
		adjustpointer(adjinfo, unsafe.Pointer(&s.elem))
	}
}

func fillstack(stk stack, b byte) {
	for p := stk.lo; p < stk.hi; p++ {
		*(*byte)(unsafe.Pointer(p)) = b
	}
}

func findsghi(gp *g, stk stack) uintptr {
	var sghi uintptr
	for sg := gp.waiting; sg != nil; sg = sg.waitlink {
		p := uintptr(sg.elem) + uintptr(sg.c.elemsize)
		if stk.lo <= p && p < stk.hi && p > sghi {
			sghi = p
		}
	}
	return sghi
}

// syncadjustsudogs调整gp的sudogs，并复制gp的部分
// 堆栈，同时与并发通道
// 操作同步。它返回复制的堆栈字节数。
func syncadjustsudogs(gp *g, used uintptr, adjinfo *adjustinfo) uintptr {
	if gp.waiting == nil {
		return 0
	}

	// 锁定通道以防止并发发送/接收。
	var lastc *hchan
	for sg := gp.waiting; sg != nil; sg = sg.waitlink {
		if sg.c != lastc {
			// 在gscan位和
			// hchan锁之间有一个排序循环。通常，我们只允许获取hchan 
			// 锁，然后获取gscan位。在这种情况下，我们已经有了gscan位。我们允许收购hchan 
			// 锁作为特例，因为死锁不可能发生，因为涉及的G必须已经被
			// 暂停。因此，我们在这里得到一个特殊的hchan锁等级
			// 比gscan低，但不允许获得除hchan以外的任何其他锁。
			lockWithRank(&sg.c.lock, lockRankHchanLeaf)
		}
		lastc = sg.c
	}

	// 调整运动狗。
	adjustsudogs(gp, adjinfo)

	// 将sudogs指向的堆栈部分复制到
	// 同时保持锁以防止在
	// 发送/接收插槽上发生竞争。
	var sgsize uintptr
	if adjinfo.sghi != 0 {
		oldBot := adjinfo.old.hi - used
		newBot := oldBot + adjinfo.delta
		sgsize = adjinfo.sghi - oldBot
		memmove(unsafe.Pointer(newBot), unsafe.Pointer(oldBot), sgsize)
	}

	// 解锁频道。
	lastc = nil
	for sg := gp.waiting; sg != nil; sg = sg.waitlink {
		if sg.c != lastc {
			unlock(&sg.c.lock)
		}
		lastc = sg.c
	}

	return sgsize
}

// 将gp的堆栈复制到不同大小的新堆栈。
// 呼叫方必须已将gp状态更改为Gcopystack。
func copystack(gp *g, newsize uintptr) {
	if gp.syscallsp != 0 {
		throw("stack growth not allowed in system call")
	}
	old := gp.stack
	if old.lo == 0 {
		throw("nil stackbase")
	}
	used := old.hi - gp.sched.sp
	// 将差异添加到gcController中。addScannableStack。
	// g0堆栈永远不会移动，因此这永远不会解释它们。
	// 如果我们没有P也可以，addScannableStack可以处理
	// 这种情况。
	gcController.addScannableStack(getg().m.p.ptr(), int64(newsize)-int64(old.hi-old.lo))

	// 分配新堆栈
	new := stackalloc(uint32(newsize))
	if stackPoisonCopy != 0 {
		fillstack(new, 0xfd)
	}
	if stackDebug >= 1 {
		print("copystack gp=", gp, " [", hex(old.lo), " ", hex(old.hi-used), " ", hex(old.hi), "]", " -> [", hex(new.lo), " ", hex(new.hi-used), " ", hex(new.hi), "]/", newsize, "\n")
	}

	// 计算调整。
	var adjinfo adjustinfo
	adjinfo.old = old
	adjinfo.delta = new.hi - old.hi

	// 调整sudogs，必要时与通道操作同步。
	ncopy := used
	if !gp.activeStackChans {
		if newsize < old.hi-old.lo && atomic.Load8(&gp.parkingOnChan) != 0 {
			// 当我们正在积极地
			// 停在一个通道上时，有人缩小这个堆栈是不安全的，但它可以安全地增长，因为我们自己这样做
			// 并且明确地不想与通道同步
			// 因为我们可能会自锁。
			throw("racy sudog adjustment due to parking on channel")
		}
		adjustsudogs(gp, &adjinfo)
	} else {
		// sudogs可能指向堆栈，gp已释放通道锁，因此其他goroutine可能正在写入gp的堆栈。找到最高的
		// 指针，这样我们就可以小心地处理那里和下面的一切。（这应该离堆栈的底部
		// 不远，所以小心处理
		// 下面的所有内容几乎没有成本。）
		adjinfo.sghi = findsghi(gp, old)

		// 与通道操作同步，并复制
		// 可能与之交互的堆栈部分。
		ncopy -= syncadjustsudogs(gp, used, &adjinfo)
	}

	// 将堆栈（或其其余部分）复制到新位置
	memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy)

	// 调整具有指向堆栈的指针的剩余结构。
	// 在回溯新的
	// 堆栈之前，我们必须完成其中的大部分工作，因为gentraceback使用它们。
	adjustctxt(gp, &adjinfo)
	adjustdefers(gp, &adjinfo)
	adjustpanics(gp, &adjinfo)
	if adjinfo.sghi != 0 {
		adjinfo.sghi += adjinfo.delta
	}

	// 将旧堆栈换成新堆栈
	gp.stack = new
	gp.stackguard0 = new.lo + _StackGuard // 注意：可能会破坏抢占请求
	gp.sched.sp = new.hi - used
	gp.stktopsp += adjinfo.delta

	// 调整新堆栈中的指针。
	gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&adjinfo)), 0)

	// 释放旧堆栈
	if stackPoisonCopy != 0 {
		fillstack(old, 0xfc)
	}
	stackfree(old)
}

// 将x取整为2的幂。
func round2(x int32) int32 {
	s := uint(0)
	for 1<<s < x {
		s++
	}
	return 1 << s
}

// 在需要更多堆栈时从运行时·morestack调用。
// 分配更大的堆栈并重新定位到新堆栈。
// 对于固定摊销成本，堆栈增长是乘法的。
// 
// g->atomicstatus将在输入时运行。
// 如果计划程序试图停止此g，则它将设置preemptStop。
// 
// 这必须是nowritebarrierrec，因为它可以作为其他nowritebarrierrec函数的堆栈增长的一部分调用，但
// 编译器不会检查这一点。
// 
// go:nowritebarrierrec 
func newstack() {
	thisg := getg()
	// TODO:再次检查所有gp。不应该是getg（）。
	if thisg.m.morebuf.g.ptr().stackguard0 == stackFork {
		throw("stack growth after fork")
	}
	if thisg.m.morebuf.g.ptr() != thisg.m.curg {
		print("runtime: newstack called from g=", hex(thisg.m.morebuf.g), "\n"+"\tm=", thisg.m, " m->curg=", thisg.m.curg, " m->g0=", thisg.m.g0, " m->gsignal=", thisg.m.gsignal, "\n")
		morebuf := thisg.m.morebuf
		traceback(morebuf.pc, morebuf.sp, morebuf.lr, morebuf.g.ptr())
		throw("runtime: wrong goroutine in newstack")
	}

	gp := thisg.m.curg

	if thisg.m.curg.throwsplit {
		// 更新syscallsp，syscallpc，以防回溯使用它们。
		morebuf := thisg.m.morebuf
		gp.syscallsp = morebuf.sp
		gp.syscallpc = morebuf.pc
		pcname, pcoff := "(unknown)", uintptr(0)
		f := findfunc(gp.sched.pc)
		if f.valid() {
			pcname = funcname(f)
			pcoff = gp.sched.pc - f.entry()
		}
		print("runtime: newstack at ", pcname, "+", hex(pcoff),
			" sp=", hex(gp.sched.sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
			"\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
			"\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")

		thisg.m.traceback = 2 // 包含运行时帧
		traceback(morebuf.pc, morebuf.sp, morebuf.lr, gp)
		throw("runtime: stack split at bad time")
	}

	morebuf := thisg.m.morebuf
	thisg.m.morebuf.pc = 0
	thisg.m.morebuf.lr = 0
	thisg.m.morebuf.sp = 0
	thisg.m.morebuf.g = 0

	// 注意：如果另一个线程
	// 即将尝试抢占gp，stackguard0可能会在脚下更改。只需阅读一次，然后立即使用相同的
	// 值及以下值。
	stackguard0 := atomic.Loaduintptr(&gp.stackguard0)

	// 对我们先发制人的地方要保守。
	// 我们感兴趣的是抢占用户Go代码，而不是运行时代码。
	// 如果我们持有锁、mallocing或先占被禁用，不要
	// 先占。
	// 这个检查在newstack中很早就出现了，所以在这种情况下，即使状态更改
	// 也不会从Grunning变为Gwaiting，再变回Gwaiting。
	// 状态更改本身可以被视为一个小的抢占，因为GC可能会将Gwaiting更改为Gscanwaiting，然后
	// 此goroutine必须等待GC完成后才能继续。
	// 如果GC在某种程度上依赖于这个goroutine（例如，
	// 它需要goroutine持有的锁），那么这个小的抢占将把
	// 变成一个真正的死锁。
	preempt := stackguard0 == stackPreempt
	if preempt {
		if !canPreemptM(thisg.m) {
			// 现在让goroutine继续运行。
			// gp->preempt已设置，因此下次将被抢占。
			gp.stackguard0 = gp.stack.lo + _StackGuard
			gogo(&gp.sched) // 永远不要返回
		}
	}

	if gp.stack.lo == 0 {
		throw("missing stack in newstack")
	}
	sp := gp.sched.sp
	if goarch.ArchFamily == goarch.AMD64 || goarch.ArchFamily == goarch.I386 || goarch.ArchFamily == goarch.WASM {
		// 调用morestack需要花费一个字。
		sp -= goarch.PtrSize
	}
	if stackDebug >= 1 || sp < gp.stack.lo {
		print("runtime: newstack sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
			"\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
			"\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")
	}
	if sp < gp.stack.lo {
		print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->status=", hex(readgstatus(gp)), "\n ")
		print("runtime: split stack overflow: ", hex(sp), " < ", hex(gp.stack.lo), "\n")
		throw("runtime: split stack overflow")
	}

	if preempt {
		if gp == thisg.m.g0 {
			throw("runtime: preempt g0")
		}
		if thisg.m.p == 0 && thisg.m.locks == 0 {
			throw("runtime: g is running but p is not")
		}

		if gp.preemptShrink {
			// 我们现在处于同步安全点，所以
			// 对挂起的堆栈进行收缩。
			gp.preemptShrink = false
			shrinkstack(gp)
		}

		if gp.preemptStop {
			preemptPark(gp) // 永远不会返回
		}

		// 像goroutine一样调用runtime。天哪。
		gopreempt_m(gp) // 永远不要返回
	}

	// 分配更大的段并移动堆栈。
	oldsize := gp.stack.hi - gp.stack.lo
	newsize := oldsize * 2

	// 确保我们的增长至少与适应新框架所需的增长相同。
	// （这只是一个优化-morestack的调用者将重新检查返回的界限。）
	if f := findfunc(gp.sched.pc); f.valid() {
		max := uintptr(funcMaxSPDelta(f))
		needed := max + _StackGuard
		used := gp.stack.hi - gp.sched.sp
		for newsize-used < needed {
			newsize *= 2
		}
	}

	if stackguard0 == stackForceMove {
		// 用于调试的强制堆栈移动。
		// 不要将堆栈加倍（否则，如果重复进行此操作，我们可能会很快用完
		// 的堆栈）。
		newsize = oldsize
	}

	if newsize > maxstacksize || newsize > maxstackceiling {
		if maxstacksize < maxstackceiling {
			print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")
		} else {
			print("runtime: goroutine stack exceeds ", maxstackceiling, "-byte limit\n")
		}
		print("runtime: sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
		throw("stack overflow")
	}

	// 必须执行goroutine才能调用newstack，
	// 因此它必须是Grunning（或Gscanrunning）。
	casgstatus(gp, _Grunning, _Gcopystack)

	// 在我们进行复制时，并发GC不会扫描堆栈，因为
	// gp处于Gcopystack状态。
	copystack(gp, newsize)
	if stackDebug >= 1 {
		print("stack grow done\n")
	}
	casgstatus(gp, _Gcopystack, _Grunning)
	gogo(&gp.sched)
}

// go:nosplit 
func nilfunc() {
	*(*uint8)(nil) = 0
}

// 调整Gobuf，就像它执行了对fn 
// 的调用一样，然后在fn中的第一条指令之前停止。
func gostartcallfn(gobuf *gobuf, fv *funcval) {
	var fn unsafe.Pointer
	if fv != nil {
		fn = unsafe.Pointer(fv.fn)
	} else {
		fn = unsafe.Pointer(abi.FuncPCABIInternal(nilfunc))
	}
	gostartcall(gobuf, fn, unsafe.Pointer(fv))
}

// isShrinkStackSafe返回尝试收缩
// gp的堆栈是否安全。只有当堆栈上所有帧都有精确的
// 指针映射时，收缩堆栈才是安全的。
func isShrinkStackSafe(gp *g) bool {
	// 如果我们在系统调用中，就不能复制堆栈。
	// 系统调用可能有指向堆栈的指针，而
	// 通常我们没有最内部的
	// 帧的精确指针映射。
	// 
	// 如果我们处于异步
	// 安全点，我们也无法复制堆栈，因为我们没有用于
	// 所有帧的精确指针映射。
	// 
	// 我们也不能*收缩*窗口中
	// goroutine调用gopark将其停在频道和
	// gp之间的堆栈。正在设置活动堆栈。
	return gp.syscallsp == 0 && !gp.asyncSafePoint && atomic.Load8(&gp.parkingOnChan) == 0
}

// 可能会缩小gp使用的堆栈。必须停止gp，我们必须拥有它的堆栈。它可能在
// /\u Grunning中，但前提是这是我们自己的用户G。
func shrinkstack(gp *g) {
	if gp.stack.lo == 0 {
		throw("missing stack in shrinkstack")
	}
	if s := readgstatus(gp); s&_Gscan == 0 {
		// 我们不通过Gscan拥有堆栈。如果这是我们自己的用户G，并且我们在
		// 系统堆栈上，我们仍然可以拥有它。
		if !(gp == getg().m.curg && getg() != getg().m.curg && s == _Grunning) {
			// 我们不拥有这个堆栈。
			throw("bad status in shrinkstack")
		}
	}
	if !isShrinkStackSafe(gp) {
		throw("shrinkstack at bad time")
	}
	// 在libcall中检查自我收缩。这些可能有伪装成uintptrs的
	// 指向堆栈的指针，但这些
	// 代码路径都应该是nosplit。
	if gp == getg().m.curg && gp.m.libcallsp != 0 {
		throw("shrinking stack in libcall")
	}

	if debug.gcshrinkstackoff > 0 {
		return
	}
	f := findfunc(gp.startpc)
	if f.valid() && f.funcID == funcID_gcBgMarkWorker {
		// 我们不允许收缩gcBgMarkWorker 
		// 堆栈（请参阅gcBgMarkWorker了解解释）。
		return
	}

	oldsize := gp.stack.hi - gp.stack.lo
	newsize := oldsize / 2
	// 不要将分配缩小到最小堆栈大小以下
	// 分配。
	if newsize < _FixedStack {
		return
	}
	// 计算当前使用的堆栈的数量，并且只计算
	// 如果gp使用的
	// 当前堆栈不足四分之一，则收缩堆栈。当前使用的堆栈包括从
	// 到SP的所有内容，以及确保
	// 有空间容纳nosplit函数的堆栈保护空间。
	avail := gp.stack.hi - gp.stack.lo
	if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 {
		return
	}

	if stackDebug > 0 {
		print("shrinking stack ", oldsize, "->", newsize, "\n")
	}

	copystack(gp, newsize)
}

// freestackspan在GC结束时释放未使用的堆栈跨度。
func freeStackSpans() {
	// 扫描堆栈池以查找空堆栈跨度。
	for order := range stackpool {
		lock(&stackpool[order].item.mu)
		list := &stackpool[order].item.span
		for s := list.first; s != nil; {
			next := s.next
			if s.allocCount == 0 {
				list.remove(s)
				s.manualFreeList = 0
				osStackFree(s)
				mheap_.freeManual(s, spanAllocStack)
			}
			s = next
		}
		unlock(&stackpool[order].item.mu)
	}

	// 自由大堆栈跨度。
	lock(&stackLarge.lock)
	for i := range stackLarge.free {
		for s := stackLarge.free[i].first; s != nil; {
			next := s.next
			stackLarge.free[i].remove(s)
			osStackFree(s)
			mheap_.freeManual(s, spanAllocStack)
			s = next
		}
	}
	unlock(&stackLarge.lock)
}

// getStackMap返回局部变量和参数活动指针映射，
// frame的堆栈对象列表。
func getStackMap(frame *stkframe, cache *pcvalueCache, debug bool) (locals, args bitvector, objs []stackObjectRecord) {
	targetpc := frame.continpc
	if targetpc == 0 {
		// 框架已失效。返回空位向量。
		return
	}

	f := frame.fn
	pcdata := int32(-1)
	if targetpc != f.entry() {
		// 回电话。如果我们在函数入口
		// 点，我们希望使用入口映射（-1），即使
		// 函数的第一条指令更改了
		// 堆栈映射。
		targetpc--
		pcdata = pcdatavalue(f, _PCDATA_StackMapIndex, targetpc, cache)
	}
	if pcdata == -1 {
		// 我们没有有效的pcdata值，但此函数可能有
		// 堆栈映射。我们很可能在函数序言中看到
		// 假设是这样，希望一切顺利。
		pcdata = 0
	}

	// 局部变量。
	size := frame.varp - frame.sp
	var minsize uintptr
	switch goarch.ArchFamily {
	case goarch.ARM64:
		minsize = sys.StackAlign
	default:
		minsize = sys.MinFrameSize
	}
	if size > minsize {
		stackid := pcdata
		stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps))
		if stkmap == nil || stkmap.n <= 0 {
			print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n")
			throw("missing stackmap")
		}
		// 如果nbit==0，则无功可做。不知道我们在哪里。
		if stkmap.nbit > 0 {
			if stackid < 0 || stackid >= stkmap.n {
				print("runtime: pcdata is ", stackid, " and ", stkmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n")
				throw("bad symbol table")
			}
			locals = stackmapdata(stkmap, stackid)
			if stackDebug >= 3 && debug {
				print("      locals ", stackid, "/", stkmap.n, " ", locals.n, " words ", locals.bytedata, "\n")
			}
		} else if stackDebug >= 3 && debug {
			print("      no locals to adjust\n")
		}
	}

	if frame.arglen > 0 {
		if frame.argmap != nil {
			// argmap在函数为reflect时设置。制造或反映。打电话。
			// 在本例中，arglen指定有多少args部分实际处于活动状态。
			// /（可以是所有的args+结果，也可以只是args。）
			args = *frame.argmap
			n := int32(frame.arglen / goarch.PtrSize)
			if n < args.n {
				args.n = n // 不要使用比arglen更多的参数。
			}
		} else {
			stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
			if stackmap == nil || stackmap.n <= 0 {
				print("runtime: frame ", funcname(f), " untyped args ", hex(frame.argp), "+", hex(frame.arglen), "\n")
				throw("missing stackmap")
			}
			if pcdata < 0 || pcdata >= stackmap.n {
				// 不知道我们在哪里
				print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n")
				throw("bad symbol table")
			}
			if stackmap.nbit > 0 {
				args = stackmapdata(stackmap, pcdata)
			}
		}
	}

	// 堆栈对象。
	if (GOARCH == "amd64" || GOARCH == "arm64" || GOARCH == "ppc64" || GOARCH == "ppc64le") && unsafe.Sizeof(abi.RegArgs{}) > 0 && frame.argmap != nil {
		// argmap在函数为reflect时设置。制造或反映。打电话。
		// 在这种情况下，我们实际上不使用argmap，但我们需要为这些包含内部/abi的帧伪造堆栈对象
		// 记录。以硬编码偏移量重新标记。
		// 此偏移量与amd64和arm64上的汇编代码匹配。
		objs = methodValueCallFrameObjs[:]
	} else {
		p := funcdata(f, _FUNCDATA_StackObjects)
		if p != nil {
			n := *(*uintptr)(p)
			p = add(p, goarch.PtrSize)
			*(*slice)(unsafe.Pointer(&objs)) = slice{array: noescape(p), len: int(n), cap: int(n)}
			// 注意：上面的noescape需要保留
			// getStackMap从“泄漏参数内容：
			// 帧”中删除。该漏洞会传播到getgcmask，然后是
			// GCMask，然后是verifyGCInfo，它会将堆栈
			// gcinfo测试转换为堆gcinfo测试：（
		}
	}

	return
}

var methodValueCallFrameObjs [1]stackObjectRecord // stackobjectinit中初始化

func stkobjinit() {
	var abiRegArgsEface any = abi.RegArgs{}
	abiRegArgsType := efaceOf(&abiRegArgsEface)._type
	if abiRegArgsType.kind&kindGCProg != 0 {
		throw("abiRegArgsType needs GC Prog, update methodValueCallFrameObjs")
	}
	// Set MethodValueCallFrameObjectJS[0].gcdataoff，以便
	ptr := uintptr(unsafe.Pointer(&methodValueCallFrameObjs[0]))
	var mod *moduledata
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if datap.gofunc <= ptr && ptr < datap.end {
			mod = datap
			break
		}
	}
	if mod == nil {
		throw("methodValueCallFrameObjs is not in a module")
	}
	methodValueCallFrameObjs[0] = stackObjectRecord{
		off:       -int32(alignUp(abiRegArgsType.size, 8)), // 它始终是本地的最高地址。
		size:      int32(abiRegArgsType.size),
		_ptrdata:  int32(abiRegArgsType.ptrdata),
		gcdataoff: uint32(uintptr(unsafe.Pointer(abiRegArgsType.gcdata)) - mod.rodata),
	}
}

// 编译器为堆栈帧中的每个堆栈对象生成stackObjectRecord。
// 此记录必须与cmd/compile/internal/liveness/plive中的生成器代码匹配。开始：发射物体。
type stackObjectRecord struct {
	// 帧中的偏移量
	// 如果为负数，则从varp偏移量
	// 如果为非负数，则从argp偏移量
	off       int32
	size      int32
	_ptrdata  int32  // ptrdata，或-ptrdata为GC prog使用
	gcdataoff uint32 // 从moduledata偏移到gcdata。rodata 
}

func (r *stackObjectRecord) useGCProg() bool {
	return r._ptrdata < 0
}

func (r *stackObjectRecord) ptrdata() uintptr {
	x := r._ptrdata
	if x < 0 {
		return uintptr(-x)
	}
	return uintptr(x)
}

// gcdata返回该类型的指针映射或GC prog。
func (r *stackObjectRecord) gcdata() *byte {
	ptr := uintptr(unsafe.Pointer(r))
	var mod *moduledata
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if datap.gofunc <= ptr && ptr < datap.end {
			mod = datap
			break
		}
	}
	// 如果你在这里因为零模而惊慌失措，
	// 你可能复制了一份stackObjectRecord。
	// 必须使用原始指针。
	res := mod.rodata + uintptr(r.gcdataoff)
	return (*byte)(unsafe.Pointer(res))
}

// 通过linkname将其导出为ABI0，以便obj可以调用它。
// 
// go:nosplit 
// go:linkname morestackc 
func morestackc() {
	throw("attempt to execute system stack code on user stack")
}
